﻿using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.QueryParsers;
using Lucene.Net.Search;
using Lucene.Net.Store;
using Lucene.Net.Search.Highlight;
using Version = Lucene.Net.Util.Version;
using System;

namespace QuocHoi_TV_Frontend.Models
{
    public class LuceneHelper
    {
        public static string _luceneDir = Path.Combine(HttpContext.Current.Request.PhysicalApplicationPath, "lucene_index");
        private static FSDirectory _directoryTemp;
        private static FSDirectory _directory
        {
            get
            {
                if (_directoryTemp == null) _directoryTemp = FSDirectory.Open(new DirectoryInfo(_luceneDir));
                if (IndexWriter.IsLocked(_directoryTemp)) IndexWriter.Unlock(_directoryTemp);
                var lockFilePath = Path.Combine(_luceneDir, "write.lock");
                if (File.Exists(lockFilePath)) File.Delete(lockFilePath);
                return _directoryTemp;
            }
        }

        // search methods
        public static IEnumerable<NewsModel> GetAllIndexRecords()
        {
            // validate search index
            if (!System.IO.Directory.EnumerateFiles(_luceneDir).Any()) return new List<NewsModel>();

            // set up lucene searcher
            var searcher = new IndexSearcher(_directory, false);
            var reader = IndexReader.Open(_directory, false);
            var docs = new List<Document>();
            
            var term = reader.TermDocs();
            // v 2.9.4: use 'hit.Doc()'
            // v 3.0.3: use 'hit.Doc'
            while (term.Next()) docs.Add(searcher.Doc(term.Doc));
            reader.Dispose();
            searcher.Dispose();
            return _mapLuceneToDataList(docs);
        }
        public static IEnumerable<NewsModel> Search(string input, string fieldName = "")
        {
            if (string.IsNullOrEmpty(input)) return new List<NewsModel>();

            var terms = input.Trim().Replace("-", " ").Split(' ')
                .Where(x => !string.IsNullOrEmpty(x)).Select(x => x.Trim() + "*");
            input = string.Join(" ", terms);
            
            return _search(input, fieldName);
        }
        private static string getHighlight(Highlighter highlighter, StandardAnalyzer analyzer, string fieldContent)
        {
            //string temp = "";
            
            //Lucene.Net.Analysis.TokenStream stream = analyzer.TokenStream("", new StringReader(fieldContent));
            //temp = highlighter.GetBestFragments(stream, fieldContent, 1, "...");
            //if (temp.Length > 0)
            //{
            //    return temp;
            //}
            //else
            //{
            //    return fieldContent;
            //}
            return fieldContent;
        }
        public static IEnumerable<NewsModel> SearchDefault(string input, string fieldName = "")
        {
            return string.IsNullOrEmpty(input) ? new List<NewsModel>() : _search(input, fieldName);
        }

        // main search method
        private static IEnumerable<NewsModel> _search(string searchQuery, string searchField = "")
        {
            // validation
            if (string.IsNullOrEmpty(searchQuery.Replace("*", "").Replace("?", ""))) return new List<NewsModel>();

            // set up lucene searcher
            using (var searcher = new IndexSearcher(_directory, false))
            {
                var hits_limit = 1000;
                var analyzer = new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_30);

                IFormatter formatter = new SimpleHTMLFormatter("<span style=\"font-weight:bold; background-color:yellow;\">", "</span>");
                SimpleFragmenter fragmenter = new SimpleFragmenter(1000);
                QueryScorer scorer = null;

                ScoreDoc[] hits;
                // search by single field
                if (!string.IsNullOrEmpty(searchField))
                {
                    var parser = new QueryParser(Lucene.Net.Util.Version.LUCENE_30, searchField, analyzer);
                    var query = parseQuery(searchQuery, parser);
                    scorer = new QueryScorer(query);
                    hits = searcher.Search(query, hits_limit).ScoreDocs;

                }
                // search by multiple fields (ordered by RELEVANCE)
                else
                {
                    var parser = new MultiFieldQueryParser(Lucene.Net.Util.Version.LUCENE_30, new[] { "NewsTitleUnsign" }, analyzer);
                    var query = parseQuery(searchQuery, parser);
                    scorer = new QueryScorer(query);
                    hits = searcher.Search(query, null, hits_limit, Sort.INDEXORDER).ScoreDocs;

                }
                Highlighter highlighter = new Highlighter(formatter, scorer);
                highlighter.TextFragmenter = fragmenter;
                var results = _mapLuceneToDataListHighligh(hits, searcher, highlighter, analyzer);
                analyzer.Close();
                searcher.Dispose();
                return results;
            }
        }
        private static Query parseQuery(string searchQuery, QueryParser parser)
        {
            Query query;
            try
            {
                query = parser.Parse(searchQuery.Trim());
            }
            catch (ParseException)
            {
                query = parser.Parse(QueryParser.Escape(searchQuery.Trim()));
            }
            return query;
        }

        private static IEnumerable<NewsModel> _mapLuceneToDataListHighligh(IEnumerable<ScoreDoc> hits, IndexSearcher searcher, Highlighter highlighter, StandardAnalyzer analyzer)
        {
            // v 2.9.4: use 'hit.doc'
            // v 3.0.3: use 'hit.Doc'
            return hits.Select(hit => _mapLuceneDocumentToDataHighligh(searcher.Doc(hit.Doc), highlighter, analyzer)).ToList();
        }
        private static NewsModel _mapLuceneDocumentToDataHighligh(Document doc, Highlighter highlighter, StandardAnalyzer analyzer)
        {
            return new NewsModel
            {

                NewsID = long.Parse(doc.Get("Id")),
                NewsTitle = getHighlight(highlighter, analyzer, doc.Get("NewsTitle")),
                NewsTitleUnsign = doc.Get("NewsTitleUnsign"),
                NewSub = doc.Get("NewSub"),
                CateId = long.Parse(doc.Get("CateId")),
                CateName = doc.Get("CateName"),
                LanguageID = int.Parse(doc.Get("LanguageID")),
                PathImgSmall = doc.Get("PathImgSmall"),
                CreateDate = DateTime.Parse(doc.Get("CreateDate")),
                IsActive = bool.Parse(doc.Get("IsActive")),
                TypeNews = int.Parse(doc.Get("TypeNews"))
            };
        } 

        // map Lucene search index to data
        private static IEnumerable<NewsModel> _mapLuceneToDataList(IEnumerable<Document> hits)
        {
            return hits.Select(_mapLuceneDocumentToData).ToList();
        }
        private static IEnumerable<NewsModel> _mapLuceneToDataList(IEnumerable<ScoreDoc> hits, IndexSearcher searcher)
        {
            // v 2.9.4: use 'hit.doc'
            // v 3.0.3: use 'hit.Doc'
            return hits.Select(hit => _mapLuceneDocumentToData(searcher.Doc(hit.Doc))).ToList();
        }
        private static NewsModel _mapLuceneDocumentToData(Document doc)
        {
            return new NewsModel
            {
                NewsID = long.Parse(doc.Get("Id")),
                NewsTitle = doc.Get("NewsTitle"),
                NewsTitleUnsign = doc.Get("NewsTitleUnsign"),
                NewSub = doc.Get("NewSub"),
                CateId = long.Parse(doc.Get("CateId")),
                CateName = doc.Get("CateName"),
                LanguageID = int.Parse(doc.Get("LanguageID")),
                PathImgSmall = doc.Get("PathImgSmall"),
                CreateDate = DateTime.Parse(doc.Get("CreateDate")),
                IsActive = bool.Parse(doc.Get("IsActive")),
                TypeNews = int.Parse(doc.Get("TypeNews"))
            };
        }

        // add/update/clear search index data 
        public static void AddUpdateLuceneIndex(News News)
        {
            AddUpdateLuceneIndex(new List<News> { News });
        }
        public static void AddUpdateLuceneIndex(IEnumerable<News> News)
        {
            // init lucene
            var analyzer = new StandardAnalyzer(Version.LUCENE_30);
            using (var writer = new IndexWriter(_directory, analyzer, IndexWriter.MaxFieldLength.UNLIMITED))
            {
                // add data to lucene search index (replaces older entries if any)
                foreach (var New in News) _addToLuceneIndex(New, writer);

                // close handles
                analyzer.Close();
                writer.Dispose();
            }
        }
        public static void ClearLuceneIndexRecord(int record_id)
        {
            // init lucene
            var analyzer = new StandardAnalyzer(Version.LUCENE_30);
            using (var writer = new IndexWriter(_directory, analyzer, IndexWriter.MaxFieldLength.UNLIMITED))
            {
                // remove older index entry
                var searchQuery = new TermQuery(new Term("Id", record_id.ToString()));
                writer.DeleteDocuments(searchQuery);

                // close handles
                analyzer.Close();
                writer.Dispose();
            }
        }
        public static bool ClearLuceneIndex()
        {
            try
            {
                var analyzer = new StandardAnalyzer(Version.LUCENE_30);
                using (var writer = new IndexWriter(_directory, analyzer, true, IndexWriter.MaxFieldLength.UNLIMITED))
                {
                    // remove older index entries
                    writer.DeleteAll();

                    // close handles
                    analyzer.Close();
                    writer.Dispose();
                }
            }
            catch (Exception)
            {
                return false;
            }
            return true;
        }
        public static void Optimize()
        {
            var analyzer = new StandardAnalyzer(Version.LUCENE_30);
            using (var writer = new IndexWriter(_directory, analyzer, IndexWriter.MaxFieldLength.UNLIMITED))
            {
                analyzer.Close();
                writer.Optimize();
                writer.Dispose();
            }
        }
        private static void _addToLuceneIndex(News News, IndexWriter writer)
        {
            // remove older index entry
            var searchQuery = new TermQuery(new Term("Id", News.NewsID.ToString()));
            writer.DeleteDocuments(searchQuery);

            // add new index entry
            var doc = new Document();
            string newSub = "";
            if (!String.IsNullOrEmpty(News.NewSub))
            {
                newSub = News.NewSub;
            }
            // add lucene fields mapped to db fields
            doc.Add(new Field("Id", News.NewsID.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));
            doc.Add(new Field("NewsTitle", News.NewsTitle, Field.Store.YES, Field.Index.ANALYZED));
            doc.Add(new Field("NewsTitleUnsign", Util.VietnameseUnsign(News.NewsTitle), Field.Store.YES, Field.Index.ANALYZED));
            doc.Add(new Field("NewSub", newSub, Field.Store.YES, Field.Index.ANALYZED));
            doc.Add(new Field("CateId", News.CateId.ToString(), Field.Store.YES, Field.Index.NO));
            doc.Add(new Field("CateName", News.Category.CateName, Field.Store.YES, Field.Index.ANALYZED));
            doc.Add(new Field("LanguageID", News.Category.LanguageID.ToString(), Field.Store.YES, Field.Index.NO));
            doc.Add(new Field("PathImgSmall", News.PathImgSmall, Field.Store.YES, Field.Index.NO));
            doc.Add(new Field("CreateDate", News.CreateDate.ToString(), Field.Store.YES, Field.Index.NO));
            doc.Add(new Field("IsActive", News.IsActive.ToString(), Field.Store.YES, Field.Index.NO));
            doc.Add(new Field("TypeNews", News.TypeNews.ToString(), Field.Store.YES, Field.Index.NO));
            // add entry to index
            writer.AddDocument(doc);
        }
    }
}